<!DOCTYPE html>
<html lang="cn" dir="auto">

<head><meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="index, follow">
<title>Hugo 部署——Github Actions | 染竹君的个人博客</title>
<meta name="keywords" content="Hugo, Github Actions, rsync">
<meta name="description" content="Github Actions 简介，并用其实现 Hugo 的部署。">
<meta name="author" content="SadBird">
<link rel="canonical" href="https://www.liyangjie.cn/posts/hobby/hugo-git-actions/">
<link crossorigin="anonymous" href="/assets/css/stylesheet.min.4b7d810bc0c98044b1c7a72962d55d7f125a07c3ed5cf31e670c3fdecc98b341.css" integrity="sha256-S32BC8DJgESxx6cpYtVdfxJaB8PtXPMeZww/3syYs0E=" rel="preload stylesheet" as="style">
<link rel="icon" href="https://www.liyangjie.cn/favicon/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="https://www.liyangjie.cn/favicon/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://www.liyangjie.cn/favicon/favicon-32x32.png">
<link rel="apple-touch-icon" href="https://www.liyangjie.cn/favicon/apple-touch-icon.png">
<link rel="mask-icon" href="https://www.liyangjie.cn/favicon/safari-pinned-tab.svg">
<meta name="theme-color" content="#2e2e33">
<meta name="msapplication-TileColor" content="#2e2e33">
<noscript>
    <style>
        #theme-toggle,
        .top-link {
            display: none;
        }

    </style>
    <style>
        @media (prefers-color-scheme: dark) {
            :root {
                --theme: rgb(29, 30, 32);
                --entry: rgb(46, 46, 51);
                --primary: rgb(218, 218, 219);
                --secondary: rgb(155, 156, 157);
                --tertiary: rgb(65, 66, 68);
                --content: rgb(196, 196, 197);
                --hljs-bg: rgb(46, 46, 51);
                --code-bg: rgb(55, 56, 62);
                --border: rgb(51, 51, 51);
            }

            .list {
                background: var(--theme);
            }

            .list:not(.dark)::-webkit-scrollbar-track {
                background: 0 0;
            }

            .list:not(.dark)::-webkit-scrollbar-thumb {
                border-color: var(--theme);
            }
        }

    </style>
</noscript>
<meta name="baidu-site-verification" content="code-9oLyeix0aK" />
<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?4a41bf85d719f0e8c3165fc76904f546";
      var s = document.getElementsByTagName("script")[0]; 
      s.parentNode.insertBefore(hm, s);
    })();
</script>


<script async src="https://www.googletagmanager.com/gtag/js?id=G-C6GDZ56F4S"></script>
<script>
var doNotTrack = false;
if (!doNotTrack) {
	window.dataLayer = window.dataLayer || [];
	function gtag(){dataLayer.push(arguments);}
	gtag('js', new Date());
	gtag('config', 'G-C6GDZ56F4S', { 'anonymize_ip': false });
}
</script>
<meta property="og:title" content="Hugo 部署——Github Actions" />
<meta property="og:description" content="Github Actions 简介，并用其实现 Hugo 的部署。" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://www.liyangjie.cn/posts/hobby/hugo-git-actions/" />
<meta property="og:image" content="https://i.loli.net/2021/09/24/COFQ9BImX2ajTVU.png" /><meta property="article:section" content="posts" />
<meta property="article:published_time" content="2021-09-21T00:46:39&#43;08:00" />
<meta property="article:modified_time" content="2021-09-21T00:46:39&#43;08:00" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://i.loli.net/2021/09/24/COFQ9BImX2ajTVU.png" />
<meta name="twitter:title" content="Hugo 部署——Github Actions"/>
<meta name="twitter:description" content="Github Actions 简介，并用其实现 Hugo 的部署。"/>


<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position":  1 ,
      "name": "Posts",
      "item": "https://www.liyangjie.cn/posts/"
    }, 
    {
      "@type": "ListItem",
      "position":  2 ,
      "name": "Hobby",
      "item": "https://www.liyangjie.cn/posts/hobby/"
    }, 
    {
      "@type": "ListItem",
      "position":  3 ,
      "name": "Hugo 部署——Github Actions",
      "item": "https://www.liyangjie.cn/posts/hobby/hugo-git-actions/"
    }
  ]
}
</script>
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Hugo 部署——Github Actions",
  "name": "Hugo 部署——Github Actions",
  "description": "Github Actions 简介，并用其实现 Hugo 的部署。",
  "keywords": [
    "Hugo", "Github Actions", "rsync"
  ],
  "articleBody": "在 Hello Hugo 文章中介绍了 Hugo 的入门，并使用 git hook 机制实现了一个简单的部署工作。在完善博客的过程中，我想实现读者在线纠错、修改文章内容的功能，需要将文章内容托管到公有的平台，最好的选择当然是 GitHub。这时候问题就来了，我关联了两个远程仓库，一个在自己的远程主机上，用于实现部署；另一个在 GitHub 上，方便读者进行在线编辑。每次在本地完成内容后，都要向两个远程仓库提交代码，这多少有些膈应，更重要的是，在线编辑的内容是要从 GitHub 上同步到我的远程主机的，显然这是不合适的。\nHugo官方提供了多种部署方式，其中，Host on GitHub 和 Deployment with Rsync 结合正好可以满足我的需求，具体实现是直接放弃自己远程主机上的仓库，并使用 GitHub Actions 进行站点的部署。\n GitHub Actions 概念 官方文档给出的定义如下： GitHub Actions\r\rAutomate, customize, and execute your software development workflows right in your repository with GitHub Actions. You can discover, create, and share actions to perform any job you’d like, including CI/CD, and combine actions in a completely customized workflow.\r\r\nGitHub Actions 是 GitHub 官方提供的一种自动化、定制化的工作流，包括了 CI/CD。关于 CI/CD 的简单理解：\n CI：持续集成（Continuous Integration），使用 Git 向代码仓库推送代码后，后台将会自动进行构建、测试等工作。 CD：持续交付（Continuous Delivery），推送完成的代码（经过了自动构建、测试等流程），最终部署到生产服务器，供客户直接使用。  GitHub Actions 中的几个重要概念如下：\n workflow：工作流，持续集成一次运行的过程，就是一个 workflow 。 enent：事件，表示触发 workflow 执行的某些特定活动，例如某些 workflow 可以在仓库中的 push、pull request 发生时开始执行。 job：任务，每个 workflow 都可以包含一个或多个 job，表示每次持续集成可以执行多个任务。 step：步骤，每个 job 可以包含一个或多个 step，表示每个任务可以由多个步骤完成。 action：动作命令，每个 step 包含多个 action，它是 workflow 的最小单元，通常是独立的脚本命令。 runner：执行 workflow 的服务器，可以使用 GitHub 内置的服务器，也可以使用自己的服务器。使用自定义的服务器时，需要在服务器上安装 GitHub Actions Runner。  下图由 GitHub官方 提供，表示 GitHub Actions 的整体结构：\n\r\r简单示例 GitHub Actions 的具体实现是在当前工作目录下创建 .github/workflows/，并在目录中添加 .yml 脚本文件，每个 .yml 文件都代表了一个 workflow。目录结构如下：\n1 2 3 4  - .github |- workflows |- workflow1.yml |- workflow2.yml   下面是 GitHub官方 提供的示例文件：\n1 2 3 4 5 6 7 8 9 10 11 12  name:learn-github-actionson:[push]jobs:check-bats-version:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v2- uses:actions/setup-node@v2with:node-version:'14'- run:npm install -g bats- run:bats -v     代码 说明     name: learn-github-actions 可选，表示 workflow 的名称，可以在 GitHub Actions 的标签中显示   on: [push] 表示触发 workflow 的事件，这里的 push 表明每次进行 push 时都会执行当前的 workflow   jobs: 表示开始定义 workflow 的具体任务   check-bats-version: 自定义的第一个 job 名称   runs-on: ubuntu-latest 表示使用GitHub提供的 runner ，系统环境为 ubuntu-latest   steps: 开始定义 check-bats-version 任务的具体 step   - uses: actions/checkout@v2 uses 表示使用在 GitHub 社区中提供的名为 actions/checkout@v2 的 workflow ，它的工作是将仓库中的代码检出并下载到 runner 中，它通常是一个 step 的起点   - uses: actions/setup-node@v2 表示使用 actions/setup-node@v2 的 workflow ，它的工作是在当前 runner　中安装 node 环境，该环境提供了 npm 命令， with 表示传入该 workflow 的参数，由该 workflow 内部使用，这里指定了 node 的版本号   - run: npm install -g bats run 表示在 runner 中执行具体的命令，这里使用 npm install -g bats 进行全局安装   - run: bats -v 执行 bats -v 命令，打印 bats 的版本    该 action 的执行流程如下图所示：\n\r\r为了使 GitHub Actions 能得到复用，GitHub 提供了发布的机制，可以将自己写好的 action 分享给其他用户使用，详细步骤可以参考 官方文档。 上面示例所示，actions/setup-node 就是一个在社区中分享的action，它表示 github.com/actions/setup-node 这个仓库，同时也是一个 action，作用是安装 Node.js，而 v2 就表示了使用的 actions/setup-node 的版本为 v2 。\nGitHub Actions费用相关\r\r 所有的公有仓库免费。 用户使用自己提供的 runner 也是免费。 私有仓库，且使用的是 GitHub 提供的 runner，收费标准见 官网。  \r\r  Hugo 部署 准备工作   远程主机配置\n部署流程使用 rsync 进行文件的同步工作， rsync 默认是基于 SSH，需要提前在自己的远程主机上安装 rsync，并准备密钥对。\n这里使用 上篇文章 中创建的 git 用户作为同步数据时使用的账户：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35  # 安装 rsync sudo apt-get install rsync cd /home/git mkdir .ssh cd .ssh # 不支持Ed25519算法的系统请使用 ssh-keygen -t rsa -b 4096 -C \"your_email@example.com\" ssh-keygen -t ed25519 -C \"your_email@example.com\" # 交互完成密钥的生成 Generating public/private ed25519 key pair. Enter file in which to save the key (/root/.ssh/id_ed25519): rsync # 输入密钥对名称，默认名id_ed25519 Enter passphrase (empty for no passphrase): # 输入密码，这里选择不输入 Enter same passphrase again: # 重复上述密码 # 成功 Your identification has been saved in rsync. Your public key has been saved in rsync.pub. The key fingerprint is: SHA256:xxxxxxxxxxxx/xxx/xxxxxxxxxxx/xxx your_email@example.com The key's randomart image is: +--[ED25519 256]--+ | | | o + .o o +| | .| | + . oo .| | | | | | + .o+ o oo.| | o. .. + +| | | +----[SHA256]-----+   密钥对生成成功后，在 .ssh 目录下新建 authorized_keys 文件，并将公钥信息增加到该文件末尾：\n1  cat rsync.pub  authorized_keys   私钥文件中的内容需要拷贝到 GitHub，稍后介绍。\n  GitHub Secrets 配置\nSecrets 是提供给 action 使用的安全变量机制，Secrets 中定义的变量都会进行加密，但后续可以在 action 中正常使用。\n进入 Hugo 博客仓库，点击 Settings，左侧找到 Secrets，进入 Secrets 配置:\n\r\r如图所示，点击右上角的 New repository secret 创建新变量：这里先创建私钥变量 REMOTE_KEY，在变量 Name 中输入 REMOTE_KEY。从服务器上拷贝私钥文件 rsync 的全部内容到 Value 中，点击 Add secret 保存：\n1 2 3 4 5  cat rsync # 拷贝输出中所有内容 -----BEGIN OPENSSH PRIVATE KEY----- ... -----END OPENSSH PRIVATE KEY-----   \r\r使用同样的方式创建剩余 Secrets：\n REMOTE_HOST：远程主机地址。 REMOTE_PORT：远程主机 SSH 端口，默认为 22 可不配置，若不是 22 必须配置。 REMOTE_USER：数据同步使用的用户，如本文中使用的 git。 REMOTE_PATH：远程主机上的目标路径，同步文件将会拷贝到该路径中，如 nginx 配置的静态网站路径。    配置 GitHub Actions 在仓库目录下创建 .github/workflows 目录，并且在目录中创建 deploy.yml 文件：\n1 2 3  - .github |- workflows |- deploy.yml   deploy.yml 中的内容如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65  name:deployon:# push事件push:# 忽略某些文件和目录，自行定义paths-ignore:- '.forestry/**'- 'archetypes/**'- '.gitignore'- '.gitmodules'- 'README.md'branches:[master ]# pull_request事件pull_request:# 忽略某些文件和目录，自行定义paths-ignore:- '.forestry/**'- 'archetypes/**'- '.gitignore'- '.gitmodules'- 'README.md'branches:[master ]# 支持手动运行workflow_dispatch:jobs:# job名称为deploydeploy:# 使用GitHub提供的runnerruns-on:ubuntu-20.04steps:# 检出代码，包括submodules，保证主题文件正常- name:Checkout sourceuses:actions/checkout@v2with:ref:mastersubmodules:true# Fetch Hugo themes (true OR recursive)fetch-depth:0# Fetch all history for .GitInfo and .Lastmod# 准备Hugo环境- name:Setup Hugouses:peaceiris/actions-hugo@v2with:hugo-version:'latest'# extended: true# Hugo构建静态站点，默认输出到public目录下 - name:Buildrun:hugo --gc --verbose --minify# 将public目录下的所有内容同步到远程服务器的nginx站点路径，注意path参数的写法，'public'和'public/'是不同的- name:Deployuses:burnett01/rsync-deployments@5.1with:switches:-avzr --deletepath:./public/remote_host:${{ secrets.REMOTE_HOST }}remote_port:${{ secrets.REMOTE_PORT }}remote_path:${{ secrets.REMOTE_PATH }}remote_user:${{ secrets.REMOTE_USER }}remote_key:${{ secrets.REMOTE_KEY }}  action 的 3 个 step 工作内容分别如下：\n 先检出代码到 runner，包括 submodule 下的内容，这是为了保证作为 submodule 的主题目录能正常使用。 actions-hugo@v2 安装准备好 Hugo 的环境，with 表示 Hugo 版本为 latest。 hugo --gc --verbose --minify 构建 Hugo 静态站点，默认输出到 public 目录中。 rsync-deployments@5.1 将 public 目录中 所有的内容 全部拷贝到服务器指定路径，这里 ${{ secrets.XXX }} 引用我们之前创建好的 Secrets 变量。注意在使用 rsync 时 public 与 public/ 的区别。前者会在目标目录中创建同名目录 public，后者不会创建该目录，而是直接将源目录public中的 所有内容 拷贝到目标目录。要了解更多关于 rsync 的使用细节可以参考 这篇文章。  检验结果 将新创建好的 action push 到 GitHub 仓库，在 Actions 标签下就可以看到所有的 action 了。\n\r\r本地修改一些代码，提交并 push 后，可以看到 deploy 这个 action 的执行过程，包括执行结果（成功或者失败）、执行时间、输出等信息。\n\r\r访问自己的站点，看看是否运行成功。若一切正常，这次的部署转移计划算是顺利完成了。\n",
  "wordCount" : "773",
  "inLanguage": "cn",
  "image":"https://i.loli.net/2021/09/24/COFQ9BImX2ajTVU.png","datePublished": "2021-09-21T00:46:39+08:00",
  "dateModified": "2021-09-21T00:46:39+08:00",
  "author":[{
    "@type": "Person",
    "name": "SadBird"
  }],
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://www.liyangjie.cn/posts/hobby/hugo-git-actions/"
  },
  "publisher": {
    "@type": "Organization",
    "name": "染竹君的个人博客",
    "logo": {
      "@type": "ImageObject",
      "url": "https://www.liyangjie.cn/favicon/favicon.ico"
    }
  }
}
</script>
</head>

<body class="">
    <a id="top"></a>
<script>
    if (localStorage.getItem("pref-theme") === "dark") {
        document.body.classList.add('dark');
    } else if (localStorage.getItem("pref-theme") === "light") {
        document.body.classList.remove('dark')
    } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
        document.body.classList.add('dark');
    }

</script>

<header class="header">
    <nav class="nav">
        <div class="logo">
            <a href="https://www.liyangjie.cn/" accesskey="h" title="染竹君的小站 (Alt + H)">
                        
                    <img src="https://www.liyangjie.cn/img/logo_hue7837a3d2f79ccdd94da0537755daebc_15738_0x30_resize_box_3.png" alt="logo" aria-label="logo"
                        height="30">染竹君的小站</a>
            <span class="logo-switches">
                <button id="theme-toggle" accesskey="t" title="(Alt + T)">
                    <svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
                    </svg>
                    <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <circle cx="12" cy="12" r="5"></circle>
                        <line x1="12" y1="1" x2="12" y2="3"></line>
                        <line x1="12" y1="21" x2="12" y2="23"></line>
                        <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
                        <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
                        <line x1="1" y1="12" x2="3" y2="12"></line>
                        <line x1="21" y1="12" x2="23" y2="12"></line>
                        <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
                        <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
                    </svg>
                </button>
            </span>
        </div>
        <ul id="menu">
            <li>
                <a href="https://www.liyangjie.cn/categories/" title="分类">
                    <span>分类</span>
                </a>
            </li>
            <li>
                <a href="https://www.liyangjie.cn/tags/" title="标签">
                    <span>标签</span>
                </a>
            </li>
            <li>
                <a href="https://www.liyangjie.cn/archives/" title="归档">
                    <span>归档</span>
                </a>
            </li>
            <li>
                <a href="https://www.liyangjie.cn/search/" title="搜索">
                    <span>搜索</span>
                </a>
            </li>
        </ul>
    </nav>
</header>
<main class="main">

<article class="post-single">
  <header class="post-header">
    <div class="breadcrumbs"><a href="https://www.liyangjie.cn/">Home</a>&nbsp;»&nbsp;<a href="https://www.liyangjie.cn/posts/">Posts</a>&nbsp;»&nbsp;<a href="https://www.liyangjie.cn/posts/hobby/">Hobby</a></div>
    <h1 class="post-title">
      Hugo 部署——Github Actions
    </h1>
    <div class="post-meta"><span title='2021-09-21 00:46:39 +0800 CST'>September 21, 2021</span>&nbsp;·&nbsp;4 min&nbsp;·&nbsp;SadBird&nbsp;|&nbsp;<a href="https://github.com/YazidLee/hugo-backup/tree/master/content/posts/hobby/Hugo%e9%83%a8%e7%bd%b2-Github%20Actions.md" rel="noopener noreferrer" target="_blank">Suggest Changes</a>
</div>
  </header> <figure class="entry-cover"><img class="lazy" src="/svg/loading.min.svg"  data-src="https://i.loli.net/2021/09/24/COFQ9BImX2ajTVU.png" loading="lazy" alt="">
        
</figure><div class="toc">
    <details >
        <summary accesskey="c" title="(Alt + C)">
            <span class="details">Table of Contents</span>
        </summary>

        <div class="inner"><ul>
                <li>
                    <a href="#github-actions" aria-label="GitHub Actions">GitHub Actions</a><ul>
                        
                <li>
                    <a href="#%e6%a6%82%e5%bf%b5" aria-label="概念">概念</a></li>
                <li>
                    <a href="#%e7%ae%80%e5%8d%95%e7%a4%ba%e4%be%8b" aria-label="简单示例">简单示例</a></li></ul>
                </li>
                <li>
                    <a href="#hugo-%e9%83%a8%e7%bd%b2" aria-label="Hugo 部署">Hugo 部署</a><ul>
                        
                <li>
                    <a href="#%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c" aria-label="准备工作">准备工作</a></li>
                <li>
                    <a href="#%e9%85%8d%e7%bd%ae-github-actions" aria-label="配置 GitHub Actions">配置 GitHub Actions</a></li>
                <li>
                    <a href="#%e6%a3%80%e9%aa%8c%e7%bb%93%e6%9e%9c" aria-label="检验结果">检验结果</a>
                </li>
            </ul>
            </li>
            </ul>
        </div>
    </details>
</div>

  <div class="post-content"><p>在 <a href="https://www.liyangjie.cn/posts/hobby/hello-hugo/">Hello Hugo</a> 文章中介绍了 Hugo 的入门，并使用 git hook 机制实现了一个简单的部署工作。在完善博客的过程中，我想实现读者在线纠错、修改文章内容的功能，需要将文章内容托管到公有的平台，最好的选择当然是 GitHub。这时候问题就来了，我关联了两个远程仓库，一个在自己的远程主机上，用于实现部署；另一个在 GitHub 上，方便读者进行在线编辑。每次在本地完成内容后，都要向两个远程仓库提交代码，这多少有些膈应，更重要的是，在线编辑的内容是要从 GitHub 上同步到我的远程主机的，显然这是不合适的。</p>
<p>Hugo官方提供了多种部署方式，其中，<a href="https://gohugo.io/hosting-and-deployment/hosting-on-github/">Host on GitHub</a> 和 <a href="https://gohugo.io/hosting-and-deployment/deployment-with-rsync/">Deployment with Rsync</a> 结合正好可以满足我的需求，具体实现是直接放弃自己远程主机上的仓库，并使用 GitHub Actions 进行站点的部署。</p>
<hr>
<h2 id="github-actions">GitHub Actions<a hidden class="anchor" aria-hidden="true" href="#github-actions">#</a></h2>
<h3 id="概念">概念<a hidden class="anchor" aria-hidden="true" href="#概念">#</a></h3>
<p>官方文档给出的定义如下：
<div class="details admonition quote open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-quote-right fa-fw"></i>GitHub Actions<i class="details-icon fas fa-angle-right fa-fw"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content">Automate, customize, and execute your software development workflows right in your repository with GitHub Actions. You can discover, create, and share actions to perform any job you&rsquo;d like, including CI/CD, and combine actions in a completely customized workflow.</div>
        </div>
    </div></p>
<p>GitHub Actions 是 GitHub 官方提供的一种自动化、定制化的工作流，包括了 CI/CD。关于 CI/CD 的简单理解：</p>
<ul>
<li>CI：持续集成（<em>Continuous Integration</em>），使用 Git 向代码仓库推送代码后，后台将会自动进行构建、测试等工作。</li>
<li>CD：持续交付（<em>Continuous Delivery</em>），推送完成的代码（经过了自动构建、测试等流程），最终部署到生产服务器，供客户直接使用。</li>
</ul>
<p>GitHub Actions 中的几个重要概念如下：</p>
<ul>
<li><code>workflow</code>：工作流，持续集成一次运行的过程，就是一个 <code>workflow</code> 。</li>
<li><code>enent</code>：事件，表示触发 <code>workflow</code> 执行的某些特定活动，例如某些 <code>workflow</code> 可以在仓库中的 <code>push</code>、<code>pull request</code> 发生时开始执行。</li>
<li><code>job</code>：任务，每个 <code>workflow</code> 都可以包含一个或多个 <code>job</code>，表示每次持续集成可以执行多个任务。</li>
<li><code>step</code>：步骤，每个 <code>job</code> 可以包含一个或多个 <code>step</code>，表示每个任务可以由多个步骤完成。</li>
<li><code>action</code>：动作命令，每个 <code>step</code> 包含多个 <code>action</code>，它是 <code>workflow</code> 的最小单元，通常是独立的脚本命令。</li>
<li><code>runner</code>：执行 <code>workflow</code> 的服务器，可以使用 GitHub 内置的服务器，也可以使用自己的服务器。使用自定义的服务器时，需要在服务器上安装 <a href="https://github.com/actions/runner">GitHub Actions Runner</a>。</li>
</ul>
<p>下图由 <a href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions">GitHub官方</a> 提供，表示 GitHub Actions 的整体结构：</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/24/Lu1MjwvToWkdxC9.png" title="https://i.loli.net/2021/09/24/Lu1MjwvToWkdxC9.png" data-thumbnail="https://i.loli.net/2021/09/24/Lu1MjwvToWkdxC9.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/24/Lu1MjwvToWkdxC9.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/24/Lu1MjwvToWkdxC9.png"
            alt="https://i.loli.net/2021/09/24/Lu1MjwvToWkdxC9.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<h3 id="简单示例">简单示例<a hidden class="anchor" aria-hidden="true" href="#简单示例">#</a></h3>
<p>GitHub Actions 的具体实现是在当前工作目录下创建 <code>.github/workflows/</code>，并在目录中添加 <code>.yml</code> 脚本文件，每个 <code>.yml</code> 文件都代表了一个 <code>workflow</code>。目录结构如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">- .github
</span></span><span class="line"><span class="cl">  |- workflows
</span></span><span class="line"><span class="cl">    |- workflow1.yml
</span></span><span class="line"><span class="cl">    |- workflow2.yml
</span></span></code></pre></td></tr></table>
</div>
</div><p>下面是 <a href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions">GitHub官方</a> 提供的示例文件：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">learn-github-actions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">push]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">check-bats-version</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/setup-node@v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">node-version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;14&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">npm install -g bats</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">bats -v</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><table>
<thead>
<tr>
<th style="text-align:left">代码</th>
<th style="text-align:left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">name: learn-github-actions</td>
<td style="text-align:left">可选，表示 workflow 的名称，可以在 GitHub Actions 的标签中显示</td>
</tr>
<tr>
<td style="text-align:left">on: [push]</td>
<td style="text-align:left">表示触发 workflow 的事件，这里的 push 表明每次进行 push 时都会执行当前的 workflow</td>
</tr>
<tr>
<td style="text-align:left">jobs:</td>
<td style="text-align:left">表示开始定义 workflow 的具体任务</td>
</tr>
<tr>
<td style="text-align:left">check-bats-version:</td>
<td style="text-align:left">自定义的第一个 job 名称</td>
</tr>
<tr>
<td style="text-align:left">runs-on: ubuntu-latest</td>
<td style="text-align:left">表示使用GitHub提供的 runner ，系统环境为 ubuntu-latest</td>
</tr>
<tr>
<td style="text-align:left">steps:</td>
<td style="text-align:left">开始定义 check-bats-version 任务的具体 step</td>
</tr>
<tr>
<td style="text-align:left">- uses: actions/checkout@v2</td>
<td style="text-align:left">uses 表示使用在 GitHub 社区中提供的名为 actions/checkout@v2 的 workflow ，它的工作是将仓库中的代码检出并下载到 runner 中，它通常是一个 step 的起点</td>
</tr>
<tr>
<td style="text-align:left">- uses: actions/setup-node@v2</td>
<td style="text-align:left">表示使用 actions/setup-node@v2 的 workflow ，它的工作是在当前 runner　中安装 node 环境，该环境提供了 npm 命令， with 表示传入该 workflow 的参数，由该 workflow 内部使用，这里指定了 node 的版本号</td>
</tr>
<tr>
<td style="text-align:left">- run: npm install -g bats</td>
<td style="text-align:left">run 表示在 runner 中执行具体的命令，这里使用 npm install -g bats 进行全局安装</td>
</tr>
<tr>
<td style="text-align:left">- run: bats -v</td>
<td style="text-align:left">执行 bats -v 命令，打印 bats 的版本</td>
</tr>
</tbody>
</table>
<p>该 action 的执行流程如下图所示：</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/24/MflcjixQ5VWZwSa.png" title="https://i.loli.net/2021/09/24/MflcjixQ5VWZwSa.png" data-thumbnail="https://i.loli.net/2021/09/24/MflcjixQ5VWZwSa.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/24/MflcjixQ5VWZwSa.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/24/MflcjixQ5VWZwSa.png"
            alt="https://i.loli.net/2021/09/24/MflcjixQ5VWZwSa.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>为了使 GitHub Actions 能得到复用，GitHub 提供了发布的机制，可以将自己写好的 action 分享给其他用户使用，详细步骤可以参考 <a href="https://docs.github.com/en/actions/creating-actions/publishing-actions-in-github-marketplace">官方文档</a>。
上面示例所示，<code>actions/setup-node</code> 就是一个在社区中分享的action，它表示 github.com/actions/setup-node 这个仓库，同时也是一个 action，作用是安装 <code>Node.js</code>，而 <code>v2</code> 就表示了使用的 <code>actions/setup-node</code> 的版本为 <code>v2</code> 。</p>
<div class="details admonition tip open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-lightbulb fa-fw"></i>GitHub Actions费用相关<i class="details-icon fas fa-angle-right fa-fw"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content"><ul>
<li>所有的公有仓库免费。</li>
<li>用户使用自己提供的 <code>runner</code> 也是免费。</li>
<li>私有仓库，且使用的是 GitHub 提供的 <code>runner</code>，收费标准见 <a href="https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions">官网</a>。</li>
</ul>
</div>
        </div>
    </div>
<hr>
<h2 id="hugo-部署">Hugo 部署<a hidden class="anchor" aria-hidden="true" href="#hugo-部署">#</a></h2>
<h3 id="准备工作">准备工作<a hidden class="anchor" aria-hidden="true" href="#准备工作">#</a></h3>
<ul>
<li>
<p>远程主机配置</p>
<p>部署流程使用 <code>rsync</code> 进行文件的同步工作， <code>rsync</code> 默认是基于 SSH，需要提前在自己的远程主机上安装 <code>rsync</code>，并准备密钥对。</p>
<p>这里使用 <a href="https://www.liyangjie.cn/posts/hobby/hello-hugo/">上篇文章</a> 中创建的 git 用户作为同步数据时使用的账户：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># 安装 rsync</span>
</span></span><span class="line"><span class="cl">sudo apt-get install rsync
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /home/git
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mkdir .ssh
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> .ssh
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 不支持Ed25519算法的系统请使用 ssh-keygen -t rsa -b 4096 -C &#34;your_email@example.com&#34;</span>
</span></span><span class="line"><span class="cl">ssh-keygen -t ed25519 -C <span class="s2">&#34;your_email@example.com&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 交互完成密钥的生成</span>
</span></span><span class="line"><span class="cl">Generating public/private ed25519 key pair.
</span></span><span class="line"><span class="cl">Enter file in which to save the key <span class="o">(</span>/root/.ssh/id_ed25519<span class="o">)</span>: rsync <span class="c1"># 输入密钥对名称，默认名id_ed25519</span>
</span></span><span class="line"><span class="cl">Enter passphrase <span class="o">(</span>empty <span class="k">for</span> no passphrase<span class="o">)</span>:  <span class="c1"># 输入密码，这里选择不输入</span>
</span></span><span class="line"><span class="cl">Enter same passphrase again:  <span class="c1"># 重复上述密码</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 成功</span>
</span></span><span class="line"><span class="cl">Your identification has been saved in rsync.
</span></span><span class="line"><span class="cl">Your public key has been saved in rsync.pub.
</span></span><span class="line"><span class="cl">The key fingerprint is:
</span></span><span class="line"><span class="cl">SHA256:xxxxxxxxxxxx/xxx/xxxxxxxxxxx/xxx your_email@example.com
</span></span><span class="line"><span class="cl">The key<span class="err">&#39;</span>s randomart image is:
</span></span><span class="line"><span class="cl">+--<span class="o">[</span>ED25519 256<span class="o">]</span>--+
</span></span><span class="line"><span class="cl"><span class="p">|</span>                 <span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span>     o +   .o o +<span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span>                .<span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span>    + .   oo    .<span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span>                 <span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span>                 <span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span>  + .o+     o oo.<span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span>   o. ..     +  +<span class="p">|</span>
</span></span><span class="line"><span class="cl"><span class="p">|</span>                 <span class="p">|</span>
</span></span><span class="line"><span class="cl">+----<span class="o">[</span>SHA256<span class="o">]</span>-----+
</span></span></code></pre></td></tr></table>
</div>
</div><p>密钥对生成成功后，在 <code>.ssh</code> 目录下新建 <code>authorized_keys</code> 文件，并将公钥信息增加到该文件末尾：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cat rsync.pub &gt;&gt; authorized_keys 
</span></span></code></pre></td></tr></table>
</div>
</div><p>私钥文件中的内容需要拷贝到 GitHub，稍后介绍。</p>
</li>
<li>
<p>GitHub Secrets 配置</p>
<p>Secrets 是提供给 action 使用的安全变量机制，Secrets 中定义的变量都会进行加密，但后续可以在 action 中正常使用。</p>
<p>进入 Hugo 博客仓库，点击 <code>Settings</code>，左侧找到 <code>Secrets</code>，进入 Secrets 配置:</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/24/zoHPdwagGpcxWO7.png" title="https://i.loli.net/2021/09/24/zoHPdwagGpcxWO7.png" data-thumbnail="https://i.loli.net/2021/09/24/zoHPdwagGpcxWO7.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/24/zoHPdwagGpcxWO7.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/24/zoHPdwagGpcxWO7.png"
            alt="https://i.loli.net/2021/09/24/zoHPdwagGpcxWO7.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>如图所示，点击右上角的 <code>New repository secret</code> 创建新变量：这里先创建私钥变量 <code>REMOTE_KEY</code>，在变量 <code>Name</code> 中输入 <code>REMOTE_KEY</code>。从服务器上拷贝私钥文件 <code>rsync</code> 的全部内容到 <code>Value</code> 中，点击 <code>Add secret</code> 保存：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">cat rsync
</span></span><span class="line"><span class="cl"><span class="c1"># 拷贝输出中所有内容</span>
</span></span><span class="line"><span class="cl">-----BEGIN OPENSSH PRIVATE KEY-----
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">-----END OPENSSH PRIVATE KEY-----
</span></span></code></pre></td></tr></table>
</div>
</div><p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/24/Omepn6wQSu8RK7U.png" title="https://i.loli.net/2021/09/24/Omepn6wQSu8RK7U.png" data-thumbnail="https://i.loli.net/2021/09/24/Omepn6wQSu8RK7U.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/24/Omepn6wQSu8RK7U.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/24/Omepn6wQSu8RK7U.png"
            alt="https://i.loli.net/2021/09/24/Omepn6wQSu8RK7U.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>使用同样的方式创建剩余 Secrets：</p>
<ul>
<li><code>REMOTE_HOST</code>：远程主机地址。</li>
<li><code>REMOTE_PORT</code>：远程主机 SSH 端口，默认为 22 可不配置，若不是 22 必须配置。</li>
<li><code>REMOTE_USER</code>：数据同步使用的用户，如本文中使用的 git。</li>
<li><code>REMOTE_PATH</code>：远程主机上的目标路径，同步文件将会拷贝到该路径中，如 nginx 配置的静态网站路径。</li>
</ul>
</li>
</ul>
<h3 id="配置-github-actions">配置 GitHub Actions<a hidden class="anchor" aria-hidden="true" href="#配置-github-actions">#</a></h3>
<p>在仓库目录下创建 <code>.github/workflows</code> 目录，并且在目录中创建 <code>deploy.yml</code> 文件：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">- .github
</span></span><span class="line"><span class="cl">  |- workflows
</span></span><span class="line"><span class="cl">    |- deploy.yml
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>deploy.yml</code> 中的内容如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># push事件</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">push</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># 忽略某些文件和目录，自行定义</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths-ignore</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;.forestry/**&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;archetypes/**&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;.gitignore&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;.gitmodules&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;README.md&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="l">master ]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># pull_request事件</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pull_request</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># 忽略某些文件和目录，自行定义</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths-ignore</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;.forestry/**&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;archetypes/**&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;.gitignore&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;.gitmodules&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;README.md&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">branches</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="l">master ]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># 支持手动运行</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">workflow_dispatch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># job名称为deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">deploy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># 使用GitHub提供的runner</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-20.04</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># 检出代码，包括submodules，保证主题文件正常</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Checkout source</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">ref</span><span class="p">:</span><span class="w"> </span><span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">submodules</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">  </span><span class="c"># Fetch Hugo themes (true OR recursive)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">fetch-depth</span><span class="p">:</span><span class="w"> </span><span class="m">0</span><span class="w">    </span><span class="c"># Fetch all history for .GitInfo and .Lastmod</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># 准备Hugo环境</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Setup Hugo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">peaceiris/actions-hugo@v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">hugo-version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;latest&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="c"># extended: true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Hugo构建静态站点，默认输出到public目录下    </span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">hugo --gc --verbose --minify</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># 将public目录下的所有内容同步到远程服务器的nginx站点路径，注意path参数的写法，&#39;public&#39;和&#39;public/&#39;是不同的</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">burnett01/rsync-deployments@5.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">switches</span><span class="p">:</span><span class="w"> </span>-<span class="l">avzr --delete</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">./public/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">remote_host</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.REMOTE_HOST }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">remote_port</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.REMOTE_PORT }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">remote_path</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.REMOTE_PATH }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">remote_user</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.REMOTE_USER }}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">remote_key</span><span class="p">:</span><span class="w"> </span><span class="l">${{ secrets.REMOTE_KEY }}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>action 的 3 个 <code>step</code> 工作内容分别如下：</p>
<ol>
<li>先检出代码到 <code>runner</code>，包括 submodule 下的内容，这是为了保证作为 submodule 的主题目录能正常使用。</li>
<li><a href="https://github.com/marketplace/actions/hugo-setup#%EF%B8%8F-create-your-workflow"><code>actions-hugo@v2</code></a> 安装准备好 Hugo 的环境，<code>with</code> 表示 Hugo 版本为 latest。</li>
<li><code>hugo --gc --verbose --minify</code> 构建 Hugo 静态站点，默认输出到 public 目录中。</li>
<li><a href="https://github.com/marketplace/actions/rsync-deployments-action"><code>rsync-deployments@5.1</code></a> 将 public 目录中 <strong>所有的内容</strong> 全部拷贝到服务器指定路径，这里 <code>${{ secrets.XXX }}</code> 引用我们之前创建好的 Secrets 变量。注意在使用 <code>rsync</code> 时 public 与 public/ 的区别。前者会在目标目录中创建同名目录 public，后者不会创建该目录，而是直接将源目录public中的 <strong>所有内容</strong> 拷贝到目标目录。要了解更多关于 <code>rsync</code> 的使用细节可以参考 <a href="https://www.ruanyifeng.com/blog/2020/08/rsync.html">这篇文章</a>。</li>
</ol>
<h3 id="检验结果">检验结果<a hidden class="anchor" aria-hidden="true" href="#检验结果">#</a></h3>
<p>将新创建好的 action push 到 GitHub 仓库，在 Actions 标签下就可以看到所有的 action 了。</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/24/6ErZavgHNf3hFXM.png" title="https://i.loli.net/2021/09/24/6ErZavgHNf3hFXM.png" data-thumbnail="https://i.loli.net/2021/09/24/6ErZavgHNf3hFXM.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/24/6ErZavgHNf3hFXM.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/24/6ErZavgHNf3hFXM.png"
            alt="https://i.loli.net/2021/09/24/6ErZavgHNf3hFXM.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>本地修改一些代码，提交并 push 后，可以看到 <code>deploy</code> 这个 action 的执行过程，包括执行结果（成功或者失败）、执行时间、输出等信息。</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/24/UdNvXwi9aHCYItS.png" title="https://i.loli.net/2021/09/24/UdNvXwi9aHCYItS.png" data-thumbnail="https://i.loli.net/2021/09/24/UdNvXwi9aHCYItS.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/24/UdNvXwi9aHCYItS.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/24/UdNvXwi9aHCYItS.png"
            alt="https://i.loli.net/2021/09/24/UdNvXwi9aHCYItS.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>访问自己的站点，看看是否运行成功。若一切正常，这次的部署转移计划算是顺利完成了。</p>


  </div>

  <footer class="post-footer">
    <ul class="post-tags">
      <li><a href="https://www.liyangjie.cn/tags/hugo/">Hugo</a></li>
      <li><a href="https://www.liyangjie.cn/tags/github-actions/">Github Actions</a></li>
      <li><a href="https://www.liyangjie.cn/tags/rsync/">rsync</a></li>
    </ul><div id="post-licensing" class="admonition warning">
    <div class="details-summary admonition-title">
        版权声明 <a rel="license" target="_blank" href="https://creativecommons.org/licenses/by/4.0/deed.zh"><i class="fab fa-creative-commons"></i> <i class="fab fa-creative-commons-by"></i></a>
    </div>
    
    <div class="details-content">
        <div class="admonition-content">
            <ul>
                <li>本文作者：SadBird。</li>
                <li>本文链接：https://www.liyangjie.cn/posts/hobby/hugo-git-actions/。</li>
                <li>许可说明：本站所有文章除特殊声明外，均使用 <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="license" target="_blank"><i class="fab fa-creative-commons"></i> <i class="fab fa-creative-commons-by"></i> CC BY 4.0 </a> 许可协议，转载请注明出处。</li>
            </ul>
        </div>
    </div>
</div>
<nav class="paginav">
  <a class="prev" href="https://www.liyangjie.cn/posts/hobby/windows-activation/">
    <span class="title"><i class="fas fa-angle-double-left"></i> Prev Page</span>
    <br>
    <span>Windows 激活——MAS</span>
  </a>
  <a class="next" href="https://www.liyangjie.cn/posts/work/threadlocal-source/">
    <span class="title">Next Page <i class="fas fa-angle-double-right"></i></span>
    <br>
    <span>ThreadLocal 分析（下）——使用和源码</span>
  </a>
</nav>

  </footer>
<footer class="tc-container" id="comment">
    <div class="tc-title"><p class="c-title">Discussion</p></div>
    <div id="tcomments"></div>
</footer>
<script crossorigin="anonymous" src="/js/twikoo.min.64322d6748f9b8b12dfb029616065f8eeed16467444adde58bab7d98c5733adf.js" integrity="sha256-ZDItZ0j5uLEt&#43;wKWFgZfju7RZGdESt3li6t9mMVzOt8="></script>
<script>
    twikoo.init({
        envId: 'https://twikoo-nu-red.vercel.app/',
        el: '#tcomments',
        region: 'ap-shenzhen', 
        
        lang: 'zh-CN', 
    });
</script>
</article>
    </main>
    
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
        <path d="M12 6H0l6-6z" />
    </svg>
</a><script>
    class Accordion {
        constructor(el) {
            
            this.el = el;
            
            this.summary = el.querySelector('summary');
            
            this.content = el.querySelector('.inner');

            
            this.animation = null;
            
            this.isClosing = false;
            
            this.isExpanding = false;
            
            this.summary.addEventListener('click', (e) => this.onClick(e));
        }

        onClick(e) {
            
            e.preventDefault();
            
            this.el.style.overflow = 'hidden';
            
            if (this.isClosing || !this.el.open) {
            this.open();
            
            } else if (this.isExpanding || this.el.open) {
            this.shrink();
            }
        }

        shrink() {
            
            this.isClosing = true;
            
            
            const startHeight = `${this.el.offsetHeight}px`;
            
            const endHeight = `${this.summary.offsetHeight}px`;
            
            
            if (this.animation) {
            
            this.animation.cancel();
            }
            
            
            this.animation = this.el.animate({
            
            height: [startHeight, endHeight]
            }, {
            duration: 400,
            easing: 'ease-out'
            });
            
            
            this.animation.onfinish = () => this.onAnimationFinish(false);
            
            this.animation.oncancel = () => this.isClosing = false;
        }

        open() {
            
            this.el.style.height = `${this.el.offsetHeight}px`;
            
            this.el.open = true;
            
            window.requestAnimationFrame(() => this.expand());
        }

        expand() {
            
            this.isExpanding = true;
            
            const startHeight = `${this.el.offsetHeight}px`;
            
            const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`;
            
            
            if (this.animation) {
            
            this.animation.cancel();
            }
            
            
            this.animation = this.el.animate({
            
            height: [startHeight, endHeight]
            }, {
            duration: 400,
            easing: 'ease-out'
            });
            
            this.animation.onfinish = () => this.onAnimationFinish(true);
            
            this.animation.oncancel = () => this.isExpanding = false;
        }

        onAnimationFinish(open) {
            
            this.el.open = open;
            
            this.animation = null;
            
            this.isClosing = false;
            this.isExpanding = false;
            
            this.el.style.height = this.el.style.overflow = '';
        }
    }

    document.querySelectorAll('.toc details').forEach((el) => {
        new Accordion(el);
    });
</script>
<a href="#comment" aria-label="go to bottom comment" title="Go to Bottom Comment (Alt + B)" class="top-link bottom-comment" id="bottom-comment" accesskey="b" style="visibility: visible; opacity: 1;">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 29.338 29.338" fill="currentcolor">
        <path d="M27.184,1.605H2.156C0.967,1.605,0,2.572,0,3.76v17.572c0,1.188,0.967,2.155,2.156,2.155h13.543
		l5.057,3.777c0.414,0.31,0.842,0.468,1.268,0.468c0.789,0,1.639-0.602,1.637-1.923v-2.322h3.523c1.188,0,2.154-0.967,2.154-2.155
		V3.76C29.338,2.572,28.371,1.605,27.184,1.605z M27.34,21.332c0,0.085-0.068,0.155-0.154,0.155h-5.523v3.955l-5.297-3.956H2.156
		c-0.086,0-0.154-0.07-0.154-0.155V3.759c0-0.085,0.068-0.155,0.154-0.155v0.001h25.029c0.086,0,0.154,0.07,0.154,0.155
		L27.34,21.332L27.34,21.332z M5.505,10.792h4.334v4.333H5.505C5.505,15.125,5.505,10.792,5.505,10.792z M12.505,10.792h4.334v4.333
		h-4.334V10.792z M19.505,10.792h4.334v4.333h-4.334V10.792z"/>
    </svg>
</a>

<script>
    let comment = document.getElementById("comment");
    let bottomToComment = document.getElementById("bottom-comment")
    
    document.addEventListener('scroll',function () {
        const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
        const offsetTop = comment.offsetTop
        const scrollTop = document.documentElement.scrollTop
        const top = offsetTop - scrollTop
        if (top <= viewPortHeight + 100) {
            bottomToComment.style.visibility = "hidden";
            bottomToComment.style.opacity = "0";
        } else {
            bottomToComment.style.visibility = "visible";
            bottomToComment.style.opacity = "1";
        }
    })
</script><footer class="footer">
    <span>&copy; 2022 <a href="https://www.liyangjie.cn/">染竹君的个人博客</a></span>
    <span>
        Powered by
        <a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
        <a href="https://git.io/hugopapermod" rel="noopener" target="_blank">PaperMod</a>
    </span>
    
</footer>

<script crossorigin="anonymous" src="/js/intersection-observer.min.c0ff09623e9f209a66c6130004e0422e1385fd8e8f8baaa14246a49c19827c68.js" integrity="sha256-wP8JYj6fIJpmxhMABOBCLhOF/Y6Pi6qhQkaknBmCfGg="></script>
<script crossorigin="anonymous" src="/js/lazyload.min.min.53e82bf9e8c145b953635b17fa3911ab6f3a8f1e4d37969aeb39d829d30fa6e9.js" integrity="sha256-U&#43;gr&#43;ejBRblTY1sX&#43;jkRq286jx5NN5aa6znYKdMPpuk="></script>


<script crossorigin="anonymous" src="/js/lightgallery.min.5d1410b8c831852c6c62b2dfad75f4e0f046a13e1826f97cd7a742523fab608c.js" integrity="sha256-XRQQuMgxhSxsYrLfrXX04PBGoT4YJvl816dCUj&#43;rYIw="></script>
<script crossorigin="anonymous" src="/js/lg-thumbnail.min.afa3995936244c14f68b85b13d657c368dc787cbcea863179950ac494dab6117.js" integrity="sha256-r6OZWTYkTBT2i4WxPWV8No3Hh8vOqGMXmVCsSU2rYRc="></script>
<script crossorigin="anonymous" src="/js/lg-zoom.min.a47b38d6f7138dce5f712f77780f339b5ec26af3bcd44950bcf736754ec88ed4.js" integrity="sha256-pHs41vcTjc5fcS93eA8zm17CavO81ElQvPc2dU7IjtQ="></script>
<script>
    const lazyLoadInstance = new LazyLoad({
        
    }); 
    
    const config = {
        selector: ".lightgallery",
        mode: "lg-slide",
        plugins: [lgZoom, lgThumbnail],
        speed: 400,
        hideBarsDelay: 2000,
        mousewheel: true,
        thumbnail: true,
        exThumbImage: "data-thumbnail",
        thumbWidth: 80,
        thumbContHeight: 80,
        mobileSettings: { controls: true, showCloseIcon: true, download: true, },
        licenseKey: "28AC9E09-3D8C45D8-8D6124E0-8FF74FF3", 
    };

    lightGallery(document.getElementsByClassName('post-content')[0], config);
</script>
<script>
    let details = document.getElementsByClassName('details')
    details = details || [];
    for (let i = 0; i < details.length; i++) {
        let element = details[i]
        const summary = element.getElementsByClassName('details-summary')[0];
        if (summary) {
            summary.addEventListener('click', () => {
                element.classList.toggle('open');
            }, false);
        }
    }
</script>

<script>
    let menu = document.getElementById('menu')
    if (menu) {
        menu.scrollLeft = localStorage.getItem("menu-scroll-position");
        menu.onscroll = function () {
            localStorage.setItem("menu-scroll-position", menu.scrollLeft);
        }
    }

    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener("click", function (e) {
            e.preventDefault();
            var id = this.getAttribute("href").substr(1);
            if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
                    behavior: "smooth"
                });
            } else {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
            }
            if (id === "top") {
                history.replaceState(null, null, " ");
            } else {
                history.pushState(null, null, `#${id}`);
            }
        });
    });

</script>
<script>
    var mybutton = document.getElementById("top-link");
    window.onscroll = function () {
        if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
            mybutton.style.visibility = "visible";
            mybutton.style.opacity = "1";
        } else {
            mybutton.style.visibility = "hidden";
            mybutton.style.opacity = "0";
        }
    };

</script>
<script>
    document.getElementById("theme-toggle").addEventListener("click", () => {
        if (document.body.className.includes("dark")) {
            document.body.classList.remove('dark');
            localStorage.setItem("pref-theme", 'light');
        } else {
            document.body.classList.add('dark');
            localStorage.setItem("pref-theme", 'dark');
        }
    })

</script>
<script>
    document.querySelectorAll('pre > code').forEach((codeblock) => {
        const container = codeblock.parentNode.parentNode;

        const copybutton = document.createElement('button');
        copybutton.classList.add('copy-code');
        copybutton.innerHTML = 'copy';

        function copyingDone() {
            copybutton.innerHTML = 'copied!';
            setTimeout(() => {
                copybutton.innerHTML = 'copy';
            }, 2000);
        }

        copybutton.addEventListener('click', (cb) => {
            if ('clipboard' in navigator) {
                navigator.clipboard.writeText(codeblock.textContent);
                copyingDone();
                return;
            }

            const range = document.createRange();
            range.selectNodeContents(codeblock);
            const selection = window.getSelection();
            selection.removeAllRanges();
            selection.addRange(range);
            try {
                document.execCommand('copy');
                copyingDone();
            } catch (e) { };
            selection.removeRange(range);
        });

        if (container.classList.contains("highlight")) {
            container.appendChild(copybutton);
        } else if (container.parentNode.firstChild == container) {
            
        } else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
            
            codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
        } else {
            
            codeblock.parentNode.appendChild(copybutton);
        }
    });
</script>


<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [['$','$'], ['\\(','\\)']],
        processEscapes: true
      }
    });
</script>

<script type="text/javascript"
  src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script></body>

</html>